package cn.blnp.net.flowable.event.bound.signal;

import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.List;

/**
 * <h3>边界事件-信号边界事件</h3>
 *
 * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
 * @version 1.0
 * @since 2025/5/8 16:58
 */
@Slf4j
@SpringBootTest
public class BoundSignalEvent {

    @Resource
    private RepositoryService repositoryService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private TaskService taskService;

    /**
     *  <p>
     *      信号边界事件会捕获与其信号事件定义引用的信号具有相同信号名称的信号。当流程流转到信号边界事件依附的流程活动（如用户任务、子流程等）时，工作流引擎会创建一个捕获事件，
     *  在其依附的流程活动的生命周期内等待一个抛出信号。该信号可以由信号中间抛出事件抛出或由API触发。信号边界事件被触发后，流程会沿其外出顺序流继续流转。如果该边界事件设置为中断，
     *  则依附的流程活动将被终止。
     *  </p>
     *  <p>
     *      信号边界事件表示为依附在流程活动边界上、带有信号事件图标的圆环。信号事件图标是未填充的，表示捕获语义。如图所示，两个用户任务边界上分别依附着一个信号边界事件，其中左侧的信号
     *  边界事件的圆环是实线，代表它是边界中断事件；右侧的信号边界事件的圆环是虚线，代表它是边界非中断事件。
     *  </p><br/><br/>
     *  <img src="https://free4.yunpng.top/2025/05/08/681c753e52fa3.png" />
     *
     *  <p>
     *      事件定义格式如下所示：
     *      <pre>
     *          {@code
     *              <!-- 定义信号 -->
     *              <signal id="theSignal" name="The Signal" />
     *              <process id="signalBoundaryEventProcess">
     *                  <!-- 定义用户任务节点 -->
     *                  <userTask id="theUserTask" name="审批"/>
     *                  <!-- 定义边界事件 -->
     *                  <boundaryEvent id="signalBoundaryEvent" name="信号边界事件" attachedToRef="theUserTask" cancelActivity="false">
     *                      <!-- 包含 signalEventDefinition子元素，代表信号边界事件 -->
     *                      <signalEventDefinition signalRef="theSignal"/>
     *                  </boundaryEvent>
     *              </process>
     *          }
     *      </pre>
     *  </p>
     *  <p>
     *      信号边界事件通过设置其attachedToRef属性将其依附在用户任务上，在它嵌入的信号事件定义中通过signalRef属性引用前面定义的信号。信号边界事件的cancelActivity属性的用法与
     *  定时器边界事件相同。在这个流程定义中，信号边界事件依附在用户任务上。此外，它也可以依附在其他流程活动上，如子流程等。
     *  </p>
     *  <p>
     *      <b style="color:red">特别注意：</b>信号边界事件在接收到指定的信号时触发。需要注意的是，信号事件是全局的，也就是说，当一个流程实例抛出一个信号时，其他不同流程定义下的流程实例
     *   也可以捕获该信号（即一处发出信号，所有信号边界事件都能接收，前提是引用了同名的信号）。如果想限制信号传播的范围，如只希望在同一个流程实例中响应该信号事件，可以通过信号事件定义中的scope属性
     *   指定，示例代码如下：
     *   <pre>
     *       <signal id="alertSignal" name="alert" flowable:scope="processInstance"/>
     *   </pre>
     *  </p>
     *  <p>
     *      在以上信号定义中，flowable:scope属性表示信号传播的范围，其默认值为global，表示全局；将其设置为processInstance，表示信号仅在当前流程实例中传播。注意，scope属性不是BPMN 2.0的标准
     *   属性，而是由Flowable扩展出来的。
     *  </p>
     *
     *  <h2>案例演示</h2>
     *  <hr/>
     *  <p>
     *      该流程为合同签订流程：“起草合同”用户任务完成后流程流转到“确认合同”用户任务，在确认合同的过程中，如果用户要修改合同，可以抛出一个信号，从而使流程流转到“修改合同”用户任务。
     *  </p>
     *  <p>
     *      <b>流程定义文件：</b>{@code flow/boundSignalEvent.bpmn20.xml}<br/>
     *      <b>流程预览图：</b><br/><br/>
     *      <img src="https://free4.yunpng.top/2025/05/08/681c777d6af3d.png" />
     *  </p>
     **/
    @Test
    public void test() {
        Deployment deploy = repositoryService.createDeployment()
                .name("信号边界事件案例")
                .addClasspathResource("flow/boundSignalEvent.bpmn20.xml")
                .deploy();
        log.info("部署流程定义：{}", deploy.getId());
        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploy.getId())
                .singleResult();
        //启动两个流程实例
        ProcessInstance procInst1 = runtimeService.startProcessInstanceById(procDef.getId());
        log.info("第1个流程实例的编号为：{}", procInst1.getId());
        ProcessInstance procInst2 = runtimeService.startProcessInstanceById(procDef.getId());
        log.info("第2个流程实例的编号为：{}", procInst2.getId());
        //将实例一进行到确认合同
        Task task1OfProcInst1 = taskService.createTaskQuery()
                .processInstanceId(procInst1.getId()).singleResult();
        taskService.complete(task1OfProcInst1.getId());
        Task task2OfProcInst1 = taskService.createTaskQuery()
                .processInstanceId(procInst1.getId()).singleResult();
        log.info("第1个流程实例当前所在用户任务为：{}", task2OfProcInst1.getName());
        //将实例二进行到确认合同
        Task task1OfProcInst2 = taskService.createTaskQuery()
                .processInstanceId(procInst2.getId()).singleResult();
        taskService.complete(task1OfProcInst2.getId());
        Task task2OfProcInst2 = taskService.createTaskQuery()
                .processInstanceId(procInst2.getId()).singleResult();
        log.info("第2个流程实例当前所在用户任务为：{}", task2OfProcInst2.getName());

        //发送合同变更信号
        runtimeService.signalEventReceived("修改合同");
        log.info("发送合同变更信号完成");
        //根据流程定义查询任务
        List<Task> tasks = taskService.createTaskQuery()
                .processDefinitionId(procDef.getId()).list();
        for(Task task : tasks) {
            log.info("编号为{}的流程实例当前所在用户任务为：{}", task.getProcessInstanceId(),
                    task.getName());
        }

        /**
         *  演示结果：
         *  2025-05-08 17:31:44.882  INFO 57808 --- [           main] c.b.n.f.e.bound.signal.BoundSignalEvent  : 部署流程定义：4179e18e-2bef-11f0-a060-50ebf6ec8f6d
         * 2025-05-08 17:31:44.938  INFO 57808 --- [           main] c.b.n.f.e.bound.signal.BoundSignalEvent  : 第1个流程实例的编号为：41c20e22-2bef-11f0-a060-50ebf6ec8f6d
         * 2025-05-08 17:31:44.949  INFO 57808 --- [           main] c.b.n.f.e.bound.signal.BoundSignalEvent  : 第2个流程实例的编号为：41c9d658-2bef-11f0-a060-50ebf6ec8f6d
         * 2025-05-08 17:31:45.008  INFO 57808 --- [           main] c.b.n.f.e.bound.signal.BoundSignalEvent  : 第1个流程实例当前所在用户任务为：确认合同
         * 2025-05-08 17:31:45.040  INFO 57808 --- [           main] c.b.n.f.e.bound.signal.BoundSignalEvent  : 第2个流程实例当前所在用户任务为：确认合同
         * 2025-05-08 17:31:45.089  INFO 57808 --- [           main] c.b.n.f.e.bound.signal.BoundSignalEvent  : 发送合同变更信号完成
         * 2025-05-08 17:31:45.092  INFO 57808 --- [           main] c.b.n.f.e.bound.signal.BoundSignalEvent  : 编号为41c20e22-2bef-11f0-a060-50ebf6ec8f6d的流程实例当前所在用户任务为：修改合同
         * 2025-05-08 17:31:45.092  INFO 57808 --- [           main] c.b.n.f.e.bound.signal.BoundSignalEvent  : 编号为41c9d658-2bef-11f0-a060-50ebf6ec8f6d的流程实例当前所在用户任务为：修改合同
         **/
    }
}
